/**
 *  @file LinkedHashTableT.h
 */
#ifndef _LINKEDHASHTABLET_H_
#define _LINKEDHASHTABLET_H_

#include "../common/CppMemPool.h"
#include "HashtableT.h"
#include "ListT.h"

namespace cppflib
{

namespace collections
{

/**
 *  A container which is both a list and a hashtable, i.e. values can be accessed
 *  sequentially or randomly.
 */
template <class KEYT, class VALT>
class LinkedHashtableT : public BaseObject
{
private:
    PRIVATE const u32_t DEFAULTSIZE = 31;
    
protected:
    HashtableT<KEYT, VALT> *pHash;
    ListCppObjT<KEYT> *pKeyList;

    /**
     *  Init with specified capacity and list
     */
    LinkedHashtableT(u32_t capacity, ListCppObjT<KEYT> *pKeyList)
    {
       pHash = new HashtableT<KEYT, VALT>(capacity);
       cppmempool::Retain(pHash);
       this->pKeyList = pKeyList;
       cppmempool::Retain(this->pKeyList);
    }

private:
    /**
     *  Init with a predefined hash table and list
     */
    LinkedHashtableT(HashtableT<KEYT, VALT> *pHash, ListCppObjT<KEYT> *pKeyList)
    {
       this->pHash = pHash;
       cppmempool::Retain(this->pHash);
       this->pKeyList = pKeyList;
       cppmempool::Retain(this->pKeyList);
    }

public:
    LinkedHashtableT(void)
    {
       pHash = new HashtableT<KEYT, VALT>(DEFAULTSIZE);
       cppmempool::Retain(pHash);
       pKeyList = new ListCppObjT<KEYT>();
       cppmempool::Retain(pKeyList);
    }

    /**
     *  Init with specified capacity
     */
    explicit LinkedHashtableT(u32_t capacity)
    {
       pHash = new HashtableT<KEYT, VALT>(capacity);
       cppmempool::Retain(pHash);
       pKeyList = new ListCppObjT<KEYT>();
       cppmempool::Retain(pKeyList);
    }

    virtual ~LinkedHashtableT(void)
    {
       cppmempool::Release(pHash);
       cppmempool::Release(pKeyList);
    }

    /**
     *  Remove all elements
     */
    void Clear()
    {
       if (pHash) 
          pHash->Clear();
       
       
       if (pKeyList)
          pKeyList->Clear();
    }

    /**
     *  Remove element with the same key (KEYT must implement BaseObject::Equals)
     */
    bool Remove(const KEYT &key)
    {
       if (pHash->Remove(key)) {
          pKeyList->Remove(key);
          return true;
       }

       return false;
    }

    /**
     *  Remove an element (throws exception if index out of bound)
     */
    void RemoveAt(int nIndex)
    {
       KEYT *pKey = pKeyList->Get(nIndex);
       pHash->Remove(*pKey);
       pKeyList->RemoveAt(nIndex);
    }

    /**
     *  Get value by key
     */
    VALT *Get(const KEYT &key) const
    {
       return pHash->Get(key);
    }

    /**
     *  Get key-value based on index (zero based) (throws exception if index out of bound)
     */
    void Get(int nIndex, const KEYT*& pKey, VALT*& pVal) const
    {
       pKey = NULL;
       pVal = NULL;

       KEYT *pTempKey = pKeyList->Get(nIndex);
       pKey = pTempKey;
       pVal = pHash->Get(*pTempKey);
    }

    /**
     *  Get key-value from head
     */
    void GetHead(const KEYT*& pKey, VALT*& pVal) const
    {
       pKey = NULL;
       pVal = NULL;

       KEYT *pTempKey = pKeyList->Get(0);
       pKey = pTempKey;
       pVal = pHash->Get(*pTempKey);
    }

    /**
     *  Get key-value from tail
     */
    void GetTail(const KEYT*& pKey, VALT*& pVal) const
    {
       pKey = NULL;
       pVal = NULL;

       int tCount = static_cast<int>(pKeyList->GetCount());
       KEYT *pTempKey = pKeyList->Get(tCount > 0 ? (tCount - 1) : 0);
       pKey = pTempKey;
       pVal = pHash->Get(*pTempKey);
    }

    /**
     *  Set value of a key. If key already exists, original value will be overwritten, otherwise, 
     *  a new element is created.
     *
     *  @param [in] pKey -- key can't be NULL
     *  @param [in] pVal -- value can be NULL
     */
    void Set(KEYT *pKey, VALT *pVal)
    {
       pHash->Set(pKey, pVal);
       if (pHash->GetCount() > pKeyList->GetCount())
          pKeyList->AddTail(pKey);
    }

    /**
     *  Get total number of elements
     */
    u32_t GetCount() const
    {
       return pKeyList->GetCount();
    }

    /**
     *  Test whether a key exists
     *
     *  @param [in] pKey -- can't be NULL
     */
    bool ContainsKey(const KEYT &key) const
    {
       return pHash->ContainsKey(key);
    }

    /**
     *  Test whether a value exists
     *
     *  @param [in] pVal -- can't be NULL
     */
    bool ContainsValue(const VALT &val) const
    {
       return pHash->ContainsValue(val);
    }

    /**
     *  Make a copy of this LinkedHashtable
     */
    LinkedHashtableT<KEYT, VALT> * Copy() const
    {
       return new LinkedHashtableT<KEYT, VALT>(this->pHash->Copy(), this->pKeyList->Copy());
    }
    
};

} // end of namespace collections


} // end of namespace cppflib


#endif

